package cn.jtfadmin.base.data.jpa.range;

import cn.jtfadmin.base.lang.common.utils.AopUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ResolvableType;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;

/**
 * 数据范围aop
 */
@Aspect
@Component
public class JpaDataRangeAspect {


    private Logger logger = LoggerFactory.getLogger(JpaDataRangeAspect.class);


    @Autowired
    private ObjectProvider<JpaDataRangeRule> dataRangeRules;

    /**
     * jpa 特殊查询，切入点
     */
    @Pointcut("execution(* org.springframework.data.jpa.repository.JpaSpecificationExecutor.*(..))")
    public void pointCut() {
    }


    @Around("pointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        //方法
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        // 方法参数
        Object[] args = point.getArgs();
        // 方法参数类型
        Class<?>[] classes = method.getParameterTypes();
        // 如果有过滤条件
        if(Arrays.stream(classes).anyMatch(t->Specification.class.isAssignableFrom(t))){
            // 获取切点对象
            Object that = point.getThis();
            // 获取切点对象的原始类型
            that = AopUtils.getTarget(that);
            Class<?> entityClazz = null;
            String entityName = "";

            if(that instanceof SimpleJpaRepository){
                Method m = ReflectionUtils.findMethod(SimpleJpaRepository.class, "getDomainClass");
                m.setAccessible(true);
                entityClazz = (Class<?>) m.invoke(that);
                entityName = entityClazz.toString();
            }else{
                logger.warn("数据过滤：未找到需要过滤实体类，不在过滤"+that);
                return point.proceed();
            }
            logger.debug("开始数据过滤,实体类:{}, 时间:{}", entityName, LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")));
            boolean filterFlag = false;
            Object[] targetArgs = new Object[args.length];
            for (int i = 0; i < classes.length; i++) {
                if (Specification.class.isAssignableFrom(classes[i])) {
                    Specification specification = (Specification<?>) args[i];
                    for (JpaDataRangeRule dataRangeRule : dataRangeRules) {
                        Class<? extends JpaDataRangeRule> aClass = dataRangeRule.getClass();
                        Class<?> type = ResolvableType.forClass(aClass).as(JpaDataRangeRule.class).getGeneric(0).resolve();
                        if(type==null || type.isAssignableFrom(entityClazz)){
                            Specification temp = dataRangeRule;
                            if(specification == null){
                                specification = temp;
                                filterFlag = true;
                            }else if(temp !=null ){
                                specification = specification.and(temp);
                                filterFlag = true;
                            }
                        }
                    }
                    targetArgs[i] = specification;
                } else {
                    targetArgs[i] = args[i];
                }
            }
            logger.debug("结束数据过滤,实体类:{}, 是否有过滤条件:{}, 时间:{}", entityName, filterFlag, LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")));
            return point.proceed(targetArgs);
        }
        return point.proceed();
    }

}

